// Jocara game project

#include "game.h"

static const int kMapSize = 1500;
static const unsigned int kNumberOfTiles = pow(kMapSize, 2);
static const uint8_t kLocalMapSize = 65;
static const unsigned int kLocalNumberOfTiles = pow(kLocalMapSize, 2);
static const uint8_t kTileSize = 70;

static Tile* global_map;
static LocalMap* main_buffer;
static LocalMap* background_buffer;

GameModes Game() {
  SDL_Event event;
  SDL_Surface* screen = SDL_GetVideoSurface();
  GameModes action = kQuit;
  global_map = new Tile[kNumberOfTiles];
  GenerateMap(0);
  main_buffer = new LocalMap();
  background_buffer = new LocalMap();
  //main_buffer->update_sync(0, 0);
  bool run = true;
  // Temp moving stuff
  Vector2D player_position = {kTileSize*(kMapSize / 2),
                              kTileSize*(kMapSize / 2)
                             };
  Vector2D ground_position = {(screen->w - kTileSize* kLocalMapSize) / 2,
                              (screen->h - kTileSize* kLocalMapSize) / 2
                             };
  Vector2D local_map_position = player_position;
  Vector2D req_local_map_position = {0, 0};

  unsigned int kLocalWonderLimit = kTileSize * (kLocalMapSize / 4);
  main_buffer->update_sync(local_map_position.x, local_map_position.y);
  while(run) {
    const Uint32 frame_started = SDL_GetTicks();
    // Event loop
    while(SDL_PollEvent(&event)) {
      switch(event.type) {
      case SDL_KEYDOWN:
        switch( event.key.keysym.sym ) {
        case SDLK_ESCAPE:
          action =  kMainMenu;
          run = false;
          break;
        default:
          break;
        }
        break;
      case SDL_QUIT:
        action =  kQuit;
        run = false;
        break;
      default:
        break;
      }
    }
    // Temp moving stuff
    uint8_t inc = 7;
    Vector2D impulse_vector = {0, 0};
    // Process pressed buttons
    Uint8* keystate = SDL_GetKeyState(NULL);
    if ( keystate[SDLK_w] == 1) impulse_vector.y -= inc;
    if ( keystate[SDLK_s] == 1) impulse_vector.y += inc;
    if ( keystate[SDLK_d] == 1) impulse_vector.x += inc;
    if ( keystate[SDLK_a] == 1) impulse_vector.x -= inc;

    AddVector(&impulse_vector, &player_position);
    if(abs(local_map_position.x - player_position.x) > kLocalWonderLimit ||
        abs(local_map_position.y - player_position.y) > kLocalWonderLimit) {
      if(background_buffer->status() == kIdle) {
        req_local_map_position.x = player_position.x - player_position.x % kTileSize;
        req_local_map_position.y = player_position.y - player_position.y % kTileSize;
        background_buffer->update(req_local_map_position.x, req_local_map_position.y);
      } else if(background_buffer->status() == kReady) {
        local_map_position = req_local_map_position;
        swap_buffers();
      }
    }
    SDL_Rect final_position = {
      ground_position.x - player_position.x + local_map_position.x,
      ground_position.y - player_position.y + local_map_position.y
    };
    SDL_BlitSurface(main_buffer->image(), NULL, screen, &final_position);
    SDL_Flip(screen);
    Wait(frame_started);
  }
// Cleaning up
  delete main_buffer;
  delete background_buffer;
  delete[] global_map;
  return action;
}

void GenerateMap(const unsigned int  seed) {
  srand(seed);
  for(unsigned int t = 0; t < kNumberOfTiles; ++t) {
    uint8_t random_number = rand() % 100;
    if(random_number < 30) {
      global_map[t].level = 0;
      global_map[t].type = kTileTypeFree;
      global_map[t].texture = kTileGround;
    } else if(random_number >= 30 && random_number < 80) {
      global_map[t].level = 1;
      global_map[t].type = kTileTypeFree;
      global_map[t].texture = kTileGrass;
    } else {
      global_map[t].level = 2;
      global_map[t].type = kTileTypeOccupied;
      global_map[t].texture = kTileWater;
    }
  }
}

/*Tile* MapData(const unsigned int x, const unsigned int y) {
  return &global_map[y*kMapSize+x];
}*/

Vector2D AbsoluteTile(const int x, const int y) {
  Vector2D tile;
  tile.x = x / kTileSize;
  tile.y = y / kTileSize;
  return tile;
}

void swap_buffers() {
  LocalMap* temp = main_buffer;
  main_buffer = background_buffer;
  background_buffer = temp;
  background_buffer->reset();
}

LocalMap::~LocalMap() {
  SDL_WaitThread(thread_, NULL);
  delete[] local_map_data_;
  SDL_FreeSurface(image_);
}

LocalMap::LocalMap() {
  status_ = kIdle;
  local_map_data_ = new Tile[kLocalNumberOfTiles];
  SDL_Surface* surf = SDL_CreateRGBSurface(SDL_HWSURFACE,
                      kLocalMapSize * kTileSize, kLocalMapSize * kTileSize,
                      32, 0, 0, 0, 0);
  image_ = SDL_DisplayFormat(surf);
  SDL_FreeSurface(surf);
  thread_ = NULL;
}

void LocalMap::update_sync(const int x_, const int y_) {
  Vector2D abs_tile = AbsoluteTile(x_, y_);
  Vector2D offset = {abs_tile.x - kLocalMapSize / 2,
                     abs_tile.y - kLocalMapSize / 2
                    };
  SDL_FillRect(image_, NULL, 0);
  if(offset.x + kMapSize - 1 >= 0 && offset.x < kMapSize &&
      offset.y + kMapSize - 1 >= 0 && offset.y < kMapSize) {
    SDL_Surface* surf = SDL_CreateRGBSurface(SDL_HWSURFACE,
                        kTileSize, kTileSize,
                        32, 0, 0, 0, 0);
    // Copy global map in to local
    for(int y = 0; y < kLocalMapSize; ++y) {
      if(y + offset.y  >= 0 && y + offset.y < kMapSize) {
        for(int x = 0; x < kLocalMapSize; ++x) {
          if(x + offset.x >= 0 && x + offset.x < kMapSize) {
            local_map_data_[y* kLocalMapSize+x] =
              global_map[(y + offset.y)*kMapSize + x + offset.x];
          }
        }
      }
    }
    for(int y = 0; y < kLocalMapSize; ++y) {
      if(y + offset.y  >= 0 && y + offset.y < kMapSize) {
        for(int x = 0; x < kLocalMapSize; ++x) {
          if(x + offset.x >= 0 && x + offset.x < kMapSize) {
            // Texture tiles
            tex_blit(surf, local_map_data_[y*kLocalMapSize+x].texture);
            apply_fades(surf, x, y);
            SDL_Rect pos = {x* kTileSize, y* kTileSize};
            SDL_BlitSurface(surf, NULL, image_, &pos);
          }
        }
      }
    }
    SDL_FreeSurface(surf);
  }
  status_ = kReady;
}

void LocalMap::update(const int x, const int y) {
  status_ = kProcessing;
  Vector2D* vec = new Vector2D();
  vec->x = x;
  vec->y = y;
  thread_ = SDL_CreateThread(BackgroundLoader, vec);
}

int BackgroundLoader(void* data) {
  Vector2D* vec = static_cast<Vector2D*>(data);
  //SDL_Delay(2000);
  background_buffer->update_sync(vec->x, vec->y);
  delete vec;
  return 0;
}

void LocalMap::apply_fades(SDL_Surface* surf, const int x, const int y) {
  std::list<AlphaNeighborhood*> matrix;
  int8_t base_level = local_map_data_[y*kLocalMapSize + x].level;
  for(int y_ = -1; y_ < 2; ++y_) {
    for(int x_ = -1; x_ < 2; ++x_) {
      if(x + x_ >= 0 && x + x_ < kLocalMapSize &&
          y + y_ >= 0 && y + y_ < kLocalMapSize) {
        Tile* this_tile = &local_map_data_[(y + y_)*kLocalMapSize + x + x_];
        if(this_tile->level > base_level) {
          AlphaNeighborhood* local_matrix = NULL;
          // Check if the entry exists
          std::list<AlphaNeighborhood*>::iterator it;
          for(it = matrix.begin(); it != matrix.end(); ++it) {
            if((*it)->tile->level == this_tile->level &&
                (*it)->tile->texture == this_tile->texture) {
              local_matrix = *it;
              break;
            }
          }
          // Create new if needed
          if(local_matrix == NULL) {
            local_matrix = new AlphaNeighborhood();
            for(int i = 0; i < 4; ++i) {
              local_matrix->plus[i] = false;
              local_matrix->ex[i] = false;
            }
            local_matrix->count_ex = 0;
            local_matrix->count_plus = 0;
            local_matrix->tile = this_tile;
            matrix.push_back(local_matrix);
          }
          // Assign dir
          if(x_ == 0 && y_ == -1)local_matrix->plus[0] = true;
          else if(x_ == 1 && y_ == 0)local_matrix->plus[1] = true;
          else if(x_ == 0 && y_ == 1)local_matrix->plus[2] = true;
          else if(x_ == -1 && y_ == 0)local_matrix->plus[3] = true;
          else if(x_ == 1 && y_ == -1)local_matrix->ex[0] = true;
          else if(x_ == 1 && y_ == 1)local_matrix->ex[1] = true;
          else if(x_ == -1 && y_ == 1)local_matrix->ex[2] = true;
          else if(x_ == -1 && y_ == -1)local_matrix->ex[3] = true;
        }
      }
    }
  }
  // Draw fades
  if(!matrix.empty()) {
    std::list<AlphaNeighborhood*>::iterator it;
    AlphaNeighborhood* c;
    matrix.sort(CompareTileLevels);
    for(it = matrix.begin(); it != matrix.end(); ++it) {
      c = *it;
      // Count hits in plus and ex
      for(int i = 0; i < 4; ++i) {
        if(c->plus[i])c->count_plus++;
        if(c->ex[i])c->count_ex++;
      }
      // Choose alpha
      AlphaMode alpha_mode;
      if(c->tile->texture == kTileWater)alpha_mode = kAlphaFade;
      else alpha_mode = kAlphaDistort;

      SDL_Surface* rotated_alpha = NULL;
      if(c->count_plus != 0) {
        switch(c->count_plus) {
        case 1:
          for(int i = 0; i < 4; ++i) {
            if(c->plus[i]) {
              rotated_alpha = rotated_fade(alpha_mode, kFadeLine, i + 2);
              break;
            }
          }
          if((c->plus[2] || c->plus[3]) && c->ex[0]) {
            SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 3);
            AlphaBlit(temp, NULL, rotated_alpha, NULL);
            SDL_FreeSurface(temp);
          }
          if((c->plus[2] || c->plus[1]) && c->ex[3]) {
            SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 2);
            AlphaBlit(temp, NULL, rotated_alpha, NULL);
            SDL_FreeSurface(temp);
          }
          if((c->plus[1] || c->plus[0]) && c->ex[2]) {
            SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 1);
            AlphaBlit(temp, NULL, rotated_alpha, NULL);
            SDL_FreeSurface(temp);
          }
          if((c->plus[3] || c->plus[0]) && c->ex[1]) {
            SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 0);
            AlphaBlit(temp, NULL, rotated_alpha, NULL);
            SDL_FreeSurface(temp);
          }
          break;
        case 2:
          if(c->plus[0] && c->plus[2]) {
            rotated_alpha = rotated_fade(alpha_mode, kFadeLine, 0);
            SDL_Surface* temp = rotated_fade(alpha_mode, kFadeLine, 2);
            AlphaBlit(temp, NULL, rotated_alpha, NULL);
            SDL_FreeSurface(temp);
          } else if(c->plus[1] && c->plus[3]) {
            rotated_alpha = rotated_fade(alpha_mode, kFadeLine, 1);
            SDL_Surface* temp = rotated_fade(alpha_mode, kFadeLine, 3);
            AlphaBlit(temp, NULL, rotated_alpha, NULL);
            SDL_FreeSurface(temp);
          } else if(c->plus[2]) {
            if(c->plus[1]) {
              rotated_alpha = rotated_fade(alpha_mode, kFadeL, 3);
              if(c->ex[3]) {
                SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 2);
                AlphaBlit(temp, NULL, rotated_alpha, NULL);
                SDL_FreeSurface(temp);
              }
            } else {
              rotated_alpha = rotated_fade(alpha_mode, kFadeL, 0);
              if(c->ex[0]) {
                SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 3);
                AlphaBlit(temp, NULL, rotated_alpha, NULL);
                SDL_FreeSurface(temp);
              }
            }
          } else if(c->plus[0]) {
            if(c->plus[1]) {
              rotated_alpha = rotated_fade(alpha_mode, kFadeL, 2);
              if(c->ex[2]) {
                SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 1);
                AlphaBlit(temp, NULL, rotated_alpha, NULL);
                SDL_FreeSurface(temp);
              }
            } else {
              rotated_alpha = rotated_fade(alpha_mode, kFadeL, 1);
              if(c->ex[1]) {
                SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, 0);
                AlphaBlit(temp, NULL, rotated_alpha, NULL);
                SDL_FreeSurface(temp);
              }
            }
          }
          break;
        case 3:
          for(int i = 0; i < 4; ++i) {
            if(!c->plus[i]) {
              rotated_alpha = rotated_fade(alpha_mode, kFadeU, i);
              break;
            }
          }
          break;
        case 4:
          rotated_alpha = rotated_fade(alpha_mode, kFadeO, 0);
          break;
        default:
          break;
        }
      } else {
        SDL_Surface* temp = SDL_CreateRGBSurface(SDL_HWSURFACE,
                            kTileSize, kTileSize, 32, 0, 0, 0, 0);
        rotated_alpha = SDL_DisplayFormatAlpha(temp);
        SDL_FreeSurface(temp);
        SDL_FillRect(rotated_alpha, NULL, 0);
        for(int i = 0; i < 4; ++i) {
          if(c->ex[i]) {
            SDL_Surface* temp = rotated_fade(alpha_mode, kFadeCorner, i + 3);
            AlphaBlit(temp, NULL, rotated_alpha, NULL);
            SDL_FreeSurface(temp);
          }
        }
      }
      tex_blit_alpha(rotated_alpha, c->tile->texture);
      SDL_BlitSurface(rotated_alpha, NULL, surf, NULL);
      SDL_FreeSurface(rotated_alpha);
      delete *it;
    }
  }
}

SDL_Surface* LocalMap::rotated_fade(AlphaMode alpha_mode, FadeType fade,
                                    uint8_t t) {
  SDL_Surface* temp = SDL_CreateRGBSurface(SDL_HWSURFACE |
                               SDL_SRCALPHA, kTileSize, kTileSize,
                               32, 0, 0, 0, 0);
  SDL_Surface* img_formatted = SDL_DisplayFormatAlpha(temp);
  SDL_FreeSurface(temp);
  SDL_FillRect(img_formatted, NULL, 0);
  SDL_Rect rect_ = {kTileSize* fade, kTileSize* alpha_mode,
                    kTileSize, kTileSize
                   };
  AlphaBlit(kData[2], &rect_, img_formatted, NULL);
  if(t % 4 != 0) {
    SDL_Surface* result = rotateSurface90Degrees (img_formatted, t);
    SDL_FreeSurface(img_formatted);
    return result;
  } else {
    return img_formatted;
  }
}

void LocalMap::tex_blit(SDL_Surface* surf, TileTexture tex) {
  switch(tex) {
  case kTileWater: {
    SDL_Rect pos = {0, 0, kTileSize, kTileSize};
    SDL_BlitSurface(kData[3], &pos, surf, NULL);
  }
  break;
  case kTileGround: {
    SDL_Rect pos = {kTileSize, 0, kTileSize, kTileSize};
    SDL_BlitSurface(kData[3], &pos, surf, NULL);
  }
  break;
  case kTileGrass: {
    SDL_Rect pos = {kTileSize * 2, 0, kTileSize, kTileSize};
    SDL_BlitSurface(kData[3], &pos, surf, NULL);
  }
  break;
  default:
    SDL_FillRect(surf, NULL, 0xFFFFFF);
    break;
  }
}

void LocalMap::tex_blit_alpha(SDL_Surface* surf, TileTexture tex) {
  SDL_Rect src_clip = {0, 0};
  switch(tex) {
  case kTileWater: {
    src_clip.x = src_clip.y = 0;
  }
  break;
  case kTileGround: {
    src_clip.x = kTileSize;
    src_clip.y = 0;
  }
  break;
  case kTileGrass: {
    src_clip.x = kTileSize * 2;
    src_clip.y = 0;
  }
  break;
  default:
    break;
  }
  // Lock the surfaces
  SDL_LockSurface(surf);
  for(int y = 0; y < kTileSize; ++y) {
    for(int x = 0; x < kTileSize; ++x) {
      unsigned int pos = y * kTileSize + x;
      unsigned int src_pos = (y + src_clip.y) * 350 + x + src_clip.x;
      uint8_t alpha = static_cast<uint8_t*> (surf->pixels)[pos*4+3];
      if(alpha != 0) {
        /*for(int i = 0; i <= 2; ++i) {
          static_cast<uint8_t*> (surf->pixels)[pos*4+i] =
            static_cast<uint8_t*> (kData[3]->pixels)[src_pos*4+i];
        }*/
        memcpy(&static_cast<uint8_t*> (surf->pixels)[pos*4],
               &static_cast<uint8_t*> (kData[3]->pixels)[src_pos*4],3);
      }
    }
  }
  // Unlock the surfaces
  SDL_UnlockSurface(surf);
}

bool CompareTileLevels(AlphaNeighborhood* a, AlphaNeighborhood* b) {
  if(a->tile->level < b->tile->level)return true;
  else return false;
}
